package internal

import (
	"fmt"
	"regexp"
	"strings"

	jsonIter "github.com/json-iterator/go"

	"github.com/coocood/freecache"
)

func getJdbcURL(url string) string {
	urlLower := strings.ToLower(url)
	jdbcURL := strings.ReplaceAll(urlLower, "//", "")
	jdbcURL = strings.ReplaceAll(jdbcURL, "/", ":")
	jdbcURL = strings.ReplaceAll(jdbcURL, "thin:", "")
	jdbcURL = strings.ReplaceAll(jdbcURL, "jtds:", "")
	jdbcURL = strings.ReplaceAll(jdbcURL, "microsoft:", "")
	jdbcURL = strings.ReplaceAll(jdbcURL, "@", "")
	// 处理mysql数据库jdbcurl
	indexQuestion := strings.Index(jdbcURL, "?")
	if -1 != indexQuestion {
		jdbcURL = jdbcURL[0:indexQuestion]
	}
	// 处理informix数据库jdbcurl
	indexInformix := strings.Index(jdbcURL, "informixserver")
	if -1 != indexInformix {
		jdbcURL = jdbcURL[0:indexInformix]
	}
	// 处理sqlserver数据库jdbcurl
	indexDatabase := strings.Index(jdbcURL, "databasename=")
	indexSemicolon := strings.Index(jdbcURL, ";")
	if -1 != indexDatabase && -1 != indexSemicolon {
		databaseName := jdbcURL[indexDatabase:]
		indexEqual := strings.Index(databaseName, "=")
		indexSemicolon2 := strings.Index(databaseName, ";")
		if -1 != indexEqual && -1 != indexSemicolon2 {
			databaseName = databaseName[indexEqual+1 : indexSemicolon2]
		}
		jdbcURL = jdbcURL[0:indexSemicolon] + ":" + databaseName
	}
	// 处理sybase数据库url
	//jdbc:sybase:tds:localhost:5007/dbname
	if strings.Contains(jdbcURL, ":tds:") {
		jdbcURL = strings.ReplaceAll(jdbcURL, ":tds:", ":")
	}
	return strings.ReplaceAll(jdbcURL, "jdbc:", "")
}

func getURLHash(url string) string {
	lastIndex := strings.LastIndex(url, "?")
	if 5 < lastIndex {
		return url[0:lastIndex]
	}
	return url
}

func getCacheURLEntity(cacheKey string, cache *freecache.Cache) *SQLURLEntity {
	entity := &SQLURLEntity{}
	entityByte, err := cache.Get([]byte(cacheKey))
	if nil == err && nil != entityByte {
		json := jsonIter.ConfigCompatibleWithStandardLibrary
		err = json.Unmarshal(entityByte, entity)
		if nil == err && "" != entity.Type {
			return entity
		}
	}
	return nil
}

// 解析传入的url，获取SQLUrlEntity，并将其值存入缓存中（DEFAULT_URLENTITY 不保存）
func generateEntity(url string, kind string, cache *freecache.Cache) *SQLURLEntity {
	urlHash := getURLHash(url)

	CacheKeyURLEntityCache := JoinStrings([]string{CacheKeyURLEntityCacheSuffix, urlHash}, "")
	entity := getCacheURLEntity(CacheKeyURLEntityCache, cache)
	if nil != entity {
		return entity
	}

	// 缓存没有，重新计算
	url = strings.ToLower(url)
	switch kind {
	case DatabaseOracle:
		entity = parseURLOracle(url)
	case DatabaseMysql:
		entity = parseURLMysql(url)
	case DatabaseSQLServer:
		entity = parseURLSQLServer(url)
	case DatabaseDB2:
		entity = parseURLDB2(url)
	case DatabasePostgreSQL:
		entity = parseURLPostgre(url)
	case DatabaseInformix:
		entity = parseURLInformix(url)
	case DatabaseJtds:
		entity = parseURLJtds(url)
	case DatabaseDm:
		entity = parseURLDmOrOscar(url)
	case DatabaseOscar:
		entity = parseURLDmOrOscar(url)
	}
	if nil != entity {
		entity.Type = kind
		json := jsonIter.ConfigCompatibleWithStandardLibrary
		valueByte, err := json.Marshal(entity)
		if err == nil {
			cacheKey := []byte(CacheKeyURLEntityCache)
			_ = cache.Set(cacheKey, valueByte, 24*3600)
		}
		return entity
	}
	return &SQLURLEntity{
		Host:     DefaultHost,
		Port:     DefaultPort,
		Database: DefaultDb,
		//Type:     getDbType(url),//java逻辑，不适合python和.net
		Type: kind,
	}
}

func parseURLOracle(url string) *SQLURLEntity {
	idx := strings.Index(url, "@")
	tmp := ""
	if -1 != idx {
		tmp = url[idx:]
	}
	if strings.HasPrefix(tmp, "@(description") {
		return getEntityWithDecription(url)
	}
	return getSQLUrlEntityWithDbURLOracle(url)

}

func getSQLUrlEntityWithDbURLOracle(url string) *SQLURLEntity {
	reg := "@(?://)?(?:\\w+://)?([^\\s:,/?]+)?(?::(\\d+))?.*?(?:[/:]([^\\s:,/?]+))?"
	pattern := regexp.MustCompile(reg)
	matcher := pattern.FindStringSubmatch(url)
	if 3 <= len(matcher) {
		entity := &SQLURLEntity{}
		if "" != matcher[1] {
			entity.Host = matcher[1]
		} else {
			entity.Host = DefaultHost
		}
		if "" != matcher[2] {
			entity.Port = matcher[2]
		} else {
			entity.Port = DefaultPort
		}
		if "" != matcher[3] {
			entity.Database = matcher[3]
		} else {
			entity.Database = DefaultDb
		}
	}
	return nil
}

/**
* 含有@Decription的url解析
* 可能存在多个address，取第一个
* jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(PORT=45372)(HOST=10.128.17.166))(CONNECT_DATA=(SID=vtest)))
* jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=10.128.17.166))(PORT=45372)(CONNECT_DATA=(SID=vtest)))
 */
func getEntityWithDecription(url string) *SQLURLEntity {
	entity := &SQLURLEntity{}
	reg := `(?i)\(host=\[?([^\s,/?\[\])]+)\]?\)[^;]*?\(port=(\d+)\).*\((?:service_name|sid)+=([^\s:,/?)]+)\)`
	pattern := regexp.MustCompile(reg)
	matcher := pattern.FindStringSubmatch(url)
	if 3 <= len(matcher) {
		entity.Host = formatIPv6Host(matcher[1])
		entity.Port = matcher[2]
		entity.Database = matcher[3]
	}
	reg = `(?i)\(port=(\d+)\)[^;]*?\(host=\[?([^\s,/?\[\])]+)\]?\).*\((?:service_name|sid)+=([^\s:,/?)]+)\)`
	pattern = regexp.MustCompile(reg)
	matcher = pattern.FindStringSubmatch(url)
	if 3 <= len(matcher) {
		entity.Host = formatIPv6Host(matcher[2])
		entity.Port = matcher[1]
		entity.Database = matcher[3]
	}
	return entity
}

/**
 * 解析mysql url
 * https://dev.mysql.com/doc/connector-j/en/connector-j-reference-configuration-properties.html
 * 通过url获取host、port、database
 * jdbc:mysql://localhost:3306/test?user=root&password=&useUnicode=true&characterEncoding=gbk&autoReconnect=true&failOverReadOnly=false
 *
 */
func parseURLMysql(url string) *SQLURLEntity {
	idx := strings.Index(url, "//")
	var tmp string
	if -1 != idx {
		tmp = url[idx:]
	} else {
		tmp = ""
	}
	if strings.HasPrefix(tmp, "//address=") {
		return getEntityWithAddress(url)
	}
	return getSQLUrlEntityWithDbURL(url)
}

// //address=(port=3307)(host=master1host)/database
func getEntityWithAddress(url string) *SQLURLEntity {
	sqlURLEntity := &SQLURLEntity{}
	reg := "(?i)//address.*?\\(host=([^\\s,/\\(\\)]+)\\)[^\\s,]*?\\(port=([\\d]+)\\).*?(?:/([^\\s:/?]+))"
	pattern := regexp.MustCompile(reg)
	matcher := pattern.FindStringSubmatch(url)
	if 2 <= len(matcher) {
		sqlURLEntity.Host = formatIPv6Host(matcher[1])
	}
	reg = "(?i)//address.*?\\(port=([\\d]+)\\)[^\\s,]*?\\(host=([^\\s,/\\(\\)]+)\\).*?(?:/([^\\s:/?]+))"
	pattern = regexp.MustCompile(reg)
	matcher = pattern.FindStringSubmatch(url)
	if len(matcher) >= 2 {
		sqlURLEntity.Host = matcher[2]
	}
	if len(matcher) >= 3 {
		sqlURLEntity.Port = matcher[1]
	}
	if len(matcher) >= 4 {
		sqlURLEntity.Database = matcher[3]
	}
	return sqlURLEntity
}

// 将IPV6中的:转换成.并将[]去掉
func formatIPv6Host(host string) string {
	if "" != host {
		host = strings.ReplaceAll(host, ":", ".")
		host = strings.ReplaceAll(host, "[\\[\\]]", "")
	}
	return host
}

// 通过常规的url获取host、port、database
func getSQLUrlEntityWithDbURL(url string) *SQLURLEntity {
	sqlURLEntity := &SQLURLEntity{}
	reg := "//([^\\s:/?,]+):*(\\d+)?.*?/([^\\s:/?,]+)?"
	pattern := regexp.MustCompile(reg)
	matcher := pattern.FindStringSubmatch(url)
	if 3 <= len(matcher) {
		if "" == matcher[2] {
			matcher[2] = DatabaseMysqlPort
		}
		if "" == matcher[3] {
			matcher[3] = DefaultDb
		}
		return &SQLURLEntity{
			Host:     matcher[1],
			Port:     matcher[2],
			Database: matcher[3],
		}
	}
	return sqlURLEntity
}

/**
 * url case:
 * jdbc:sqlserver://localhost:1433;databaseName=master;
 * jdbc:sqlserver://local\/host:1433;databaseName=master;
 * jdbc:sqlserver://;servername=server_name;integratedSecurity=true;
 * @param url
 */
func parseURLSQLServer(url string) *SQLURLEntity {
	entity := &SQLURLEntity{}
	var host string
	var database string
	port := "1433" // default port number
	url = url[strings.Index(url, "//")+2:]
	st := strings.Split(url, ";")
	for index, keyValue := range st {
		if 0 == index {
			// In general, the first token includes server and port
			if -1 == strings.Index(keyValue, "=") {
				colonIndex := strings.Index(keyValue, ":")
				if -1 < colonIndex {
					host = keyValue[0:colonIndex]
					port = keyValue[colonIndex+1:]
				} else {
					host = keyValue
				}
				// Next, handle the instance by key= (// or / or \\)
				host = truncateByKey(host, "\\")
				host = truncateByKey(host, "/")
			}
		}
		eqIndex := strings.Index(keyValue, "=")
		if 0 > eqIndex {
			continue
		}
		param := keyValue[0:eqIndex]
		value := keyValue[eqIndex+1:]
		paramLower := strings.ToLower(param)
		if "servername" == paramLower {
			host = value
		} else if "databasename" == paramLower || "database" == paramLower {
			database = value
		}
	}
	entity.Host = formatIPv6Host(host)
	entity.Database = database
	entity.Port = port
	return entity
}

func truncateByKey(source string, key string) string {
	if "" == source {
		return ""
	}
	index := strings.Index(source, key)
	if -1 < index {
		return source[0:index]
	}
	return source
}

//jdbc:db2://127.0.0.1:50000/dbname
func parseURLDB2(url string) *SQLURLEntity {
	sqlURLEntity := &SQLURLEntity{}
	if strings.Contains(url, "://") {
		return parseURLWithDbURL(url)
	}
	sqlURLEntity = parseURLWithOnlyDb(url)
	return sqlURLEntity
}

/**
 * jdbc:db2://127.0.0.1:50000/SAMPLE
 * jdbc:db2://127.0.0.1/SAMPLE
 * jdbc:db2://[fec0:ffff:ffff:8000:20e:cff:fe50:39c8]:50000/sample
 * jdbc:derby:net://localhost:1527/sample:user=judy;password=no12see;retrieveMessagesFromServerOnGetMessage=true;
 * jdbc:derby:net://localhost:1527/"c:/my-db-dir/my-db-name":user=judy;password=no12see;retrieveMessagesFromServerOnGetMessage=true;
 */
func parseURLWithDbURL(url string) *SQLURLEntity {
	reg := "://(\\[[a-fA-F\\d:.]+\\]|[^\\s/;:]+)?(?::(\\d+))?.*(?:/([^\\s;\":]+)?)"
	pattern := regexp.MustCompile(reg)
	matcher := pattern.FindStringSubmatch(url)
	if 3 <= len(matcher) {
		entity := &SQLURLEntity{}
		entity.Host = DefaultHost
		if "" != matcher[1] {
			entity.Host = formatIPv6Host(matcher[1])
		}
		entity.Port = DefaultPort
		if "" != matcher[2] {
			entity.Port = matcher[2]
		}
		entity.Database = DefaultDb
		if "" != matcher[3] {
			entity.Database = matcher[3]
		}
		return entity
	}
	return nil
}

/**
 * 解析 jdbc:db2:sample
 */
func parseURLWithOnlyDb(url string) *SQLURLEntity {
	sqlURLEntity := &SQLURLEntity{}

	idx := strings.Index(url, "db2:")
	if 0 < idx {
		database := url[idx+4:]
		return &SQLURLEntity{
			Host:     DefaultHost,
			Port:     DatabaseDB2Port,
			Database: database,
		}
	}
	return sqlURLEntity
}

/**
 * jdbc:postgresql://localhost/test?user=fred&password=secret&ssl=true";
 * jdbc:postgresql:database
 * jdbc:postgresql:/
 * jdbc:postgresql://host/database
 * jdbc:postgresql://host/
 * jdbc:postgresql://host:5433/database
 * jdbc:postgresql://host:5433/
 * jdbc:postgresql://[::1]:5740/accounting
 * jdbc:postgresql://host1:5433,host2:5434/database
 * jdbc:postgresql://localhost/test?user=fred&password=secret&ssl=true";
 *
 * @param url
 * @return
 */
func parseURLPostgre(url string) *SQLURLEntity {
	entity := &SQLURLEntity{}
	reg := "(?i)postgresql:([^\\s:/]+)?(?://)?(?:(\\[[:a-fA-F\\d.]+\\]|[^\\s:/]+|))?(?::(\\d+))?[^\\s/]*(?:/([^\\s?/]+))?"
	pattern := regexp.MustCompile(reg)
	matcher := pattern.FindStringSubmatch(url)
	if 4 <= len(matcher) {
		entity.Host = DefaultHost
		if "" != matcher[2] {
			entity.Host = formatIPv6Host(matcher[2])
		}
		entity.Port = DefaultPort
		if "" != matcher[3] {
			entity.Port = matcher[3]
		}
		entity.Database = DefaultDb
		if "" != matcher[4] {
			entity.Database = matcher[4]
		} else if "" != matcher[1] {
			entity.Database = matcher[1]
		}
	}
	return entity
}

func parseURLInformix(url string) *SQLURLEntity {
	if strings.HasPrefix(url, "jdbc:informix-direct:") {
		return getEntityWithDirect(url)
	}
	return getEntityWithDbURL(url)
}

/**
 * 处理
 * jdbc:informix-direct://testDB;user=rdtest;password=test
 */
func getEntityWithDirect(url string) *SQLURLEntity {
	entity := &SQLURLEntity{}
	idx := strings.Index(url, "://")
	if 0 <= idx {
		endx := strings.Index(url, ";")
		var database string
		if 0 < endx {
			database = url[idx+3 : endx]
		} else {
			database = url[idx+3:]
		}
		entity.Host = DefaultHost
		entity.Port = DefaultPort
		entity.Database = database
	}
	return entity
}

/**
 * jdbc:informix-sqli://123.45.67.89:1533/myDB:INFORMIXSERVER=myserver;user=testuser;password=testpassword
 * jdbc:informix-sqli://123.45.67.89:11/mydb::informixserver=ss;
 * jdbc:informix-sqli://3ffe:ffff:ffff:ffff:0::12:8088:informixserver=testDB
 * @param url
 * @return
 */
func getEntityWithDbURL(url string) *SQLURLEntity {
	entity := &SQLURLEntity{}
	reg := "(?i)(?::informixserver=([^\\s=;]+)[^\\s]+)?://(?:([a-fA-F\\d.:]+):(\\d+)|([^\\s/;:]+)(?::(\\d+):?)?)?(?:/([^\\s;:]+))?(?::informixserver=([^\\s=;]+))?"
	pattern := regexp.MustCompile(reg)
	matcher := pattern.FindStringSubmatch(url)
	if 7 <= len(matcher) {
		entity.Host = DefaultHost
		entity.Port = DefaultPort
		entity.Database = DefaultDb
		if "" != matcher[2] {
			entity.Host = formatIPv6Host(matcher[2])
			if "" != matcher[3] {
				entity.Port = matcher[3]
			} else if "" != matcher[5] {
				entity.Port = matcher[5]
			}
			if matcher[6] != "" {
				entity.Database = matcher[6]
			} else if matcher[7] != "" {
				entity.Database = matcher[7]
			} else if matcher[1] != "" {
				entity.Database = matcher[1]
			}
		} else if "" != matcher[4] {
			entity.Host = formatIPv6Host(matcher[4])
		}
	}
	return entity
}

//jdbc:jtds:sqlserver://127.0.0.1:1433;databasename=TestDB
//jdbc:jtds:sqlserver://127.0.0.1:34/;databasename=TestDB;servername=localhost
func parseURLJtds(url string) *SQLURLEntity {
	entity := &SQLURLEntity{}
	var host string
	var database string
	port := fmt.Sprint(DatabaseJtdsPort)
	url = url[strings.Index(url, "//")+2:]
	st := strings.Split(url, ";")
	for index, keyValue := range st {
		if 0 == index {
			if !strings.Contains(keyValue, "=") {
				colonIndex := strings.Index(keyValue, ":")
				if -1 < colonIndex {
					host = keyValue[0:colonIndex]
				} else {
					host = keyValue
				}
				// Next, handle the instance by key= (// or / or \\)
				host = truncateByKey(host, "\\")
				host = truncateByKey(host, "/")
				// the default port number is 1433
				temp := keyValue[colonIndex+1:]
				if strings.Contains(temp, "/") {
					temp = temp[0:strings.Index(temp, "/")]
				}
				if -1 < colonIndex {
					port = temp
				}
			}
		}
		if strings.Contains(keyValue, "=") {
			eqIndex := strings.Index(keyValue, "=")
			param := keyValue[0:eqIndex]
			value := keyValue[eqIndex+1:]
			if "servername" == strings.ToLower(param) {
				host = value
			} else if "databasename" == strings.ToLower(param) || "database" == strings.ToLower(param) {
				database = value
			}
		} else if strings.Contains(keyValue, "/") {
			database = keyValue[strings.Index(keyValue, "/")+1:]
		}
	}
	if "" != host {
		entity.Host = formatIPv6Host(host)
	}
	entity.Port = port
	if "" != database {
		entity.Database = database
	}
	return entity
}

//jdbc:oscar://127.0.0.1:2003/osrdb?usedispatch=true&transactionDispatchStrategy=2
func parseURLDmOrOscar(url string) *SQLURLEntity {
	entity := &SQLURLEntity{}
	reg := "//([^\\s:/?,]+):*(\\d+)?.*?/([^\\s:/?,]+)?"
	pattern := regexp.MustCompile(reg)
	matcher := pattern.FindStringSubmatch(url)
	if 3 <= len(matcher) {
		entity.Host = matcher[1]
		if "" != matcher[2] {
			entity.Port = matcher[2]
		}
		if "" != matcher[3] {
			entity.Database = matcher[3]
		}
	}
	return entity
}
